"use client"
import type { ReactNode } from "react"
import React, { useEffect, useRef } from "react"

export interface BaseParticle {
   element: HTMLElement | SVGSVGElement
   left: number
   size: number
   top: number
}

export interface BaseParticleOptions {
   particle?: string
   size?: number
}

export interface CoolParticle extends BaseParticle {
   direction: number
   speedHorz: number
   speedUp: number
   spinSpeed: number
   spinVal: number
}

export interface CoolParticleOptions extends BaseParticleOptions {
   particleCount?: number
   speedHorz?: number
   speedUp?: number
}

function getContainer() {
   const id = "_coolMode_effect"
   const existingContainer = document.getElementById(id)

   if (existingContainer) return existingContainer

   const container = document.createElement("div")
   container.setAttribute("id", id)
   container.setAttribute(
      "style",
      "overflow:hidden; position:fixed; height:100%; top:0; left:0; right:0; bottom:0; pointer-events:none; z-index:2147483647",
   )

   document.body.appendChild(container)

   return container
}

let instanceCounter = 0

function applyParticleEffect(
   element: HTMLElement,
   options?: CoolParticleOptions,
): () => void {
   instanceCounter++

   const defaultParticle = "circle"
   const particleType = options?.particle || defaultParticle
   const sizes = [15, 20, 25, 35, 45]
   const limit = 45

   let particles: CoolParticle[] = []
   let autoAddParticle = false
   let mouseX = 0
   let mouseY = 0

   const container = getContainer()

   function generateParticle() {
      const size =
         options?.size || sizes[Math.floor(Math.random() * sizes.length)]
      const speedHorz = options?.speedHorz || Math.random() * 10
      const speedUp = options?.speedUp || Math.random() * 25
      const spinVal = Math.random() * 360
      const spinSpeed = Math.random() * 35 * (Math.random() <= 0.5 ? -1 : 1)
      const top = mouseY - size / 2
      const left = mouseX - size / 2
      const direction = Math.random() <= 0.5 ? -1 : 1

      const particle = document.createElement("div")

      if (particleType === "circle") {
         const svgNS = "http://www.w3.org/2000/svg"
         const circleSVG = document.createElementNS(svgNS, "svg")
         const circle = document.createElementNS(svgNS, "circle")
         circle.setAttributeNS(null, "cx", (size / 2).toString())
         circle.setAttributeNS(null, "cy", (size / 2).toString())
         circle.setAttributeNS(null, "r", (size / 2).toString())
         circle.setAttributeNS(
            null,
            "fill",
            `hsl(${Math.random() * 360}, 70%, 50%)`,
         )

         circleSVG.appendChild(circle)
         circleSVG.setAttribute("width", size.toString())
         circleSVG.setAttribute("height", size.toString())

         particle.appendChild(circleSVG)
      } else {
         particle.innerHTML = `<img src="${particleType}" width="${size}" height="${size}" style="border-radius: 50%">`
      }

      particle.style.position = "absolute"
      particle.style.transform = `translate3d(${left}px, ${top}px, 0px) rotate(${spinVal}deg)`

      container.appendChild(particle)

      particles.push({
         direction,
         element: particle,
         left,
         size,
         speedHorz,
         speedUp,
         spinSpeed,
         spinVal,
         top,
      })
   }

   function refreshParticles() {
      particles.forEach((p) => {
         p.left = p.left - p.speedHorz * p.direction
         p.top = p.top - p.speedUp
         p.speedUp = Math.min(p.size, p.speedUp - 1)
         p.spinVal = p.spinVal + p.spinSpeed

         if (
            p.top >=
            Math.max(window.innerHeight, document.body.clientHeight) + p.size
         ) {
            particles = particles.filter((o) => o !== p)
            p.element.remove()
         }

         p.element.setAttribute(
            "style",
            [
               "position:absolute",
               "will-change:transform",
               `top:${p.top}px`,
               `left:${p.left}px`,
               `transform:rotate(${p.spinVal}deg)`,
            ].join(";"),
         )
      })
   }

   let animationFrame: number | undefined

   let lastParticleTimestamp = 0
   const particleGenerationDelay = 30

   function loop() {
      const currentTime = performance.now()
      if (
         autoAddParticle &&
         particles.length < limit &&
         currentTime - lastParticleTimestamp > particleGenerationDelay
      ) {
         generateParticle()
         lastParticleTimestamp = currentTime
      }

      refreshParticles()
      animationFrame = requestAnimationFrame(loop)
   }

   loop()

   const isTouchInteraction = "ontouchstart" in window

   const tap = isTouchInteraction ? "touchstart" : "mousedown"
   const tapEnd = isTouchInteraction ? "touchend" : "mouseup"
   const move = isTouchInteraction ? "touchmove" : "mousemove"

   const updateMousePosition = (e: MouseEvent | TouchEvent) => {
      if ("touches" in e) {
         mouseX = e.touches?.[0].clientX
         mouseY = e.touches?.[0].clientY
      } else {
         mouseX = e.clientX
         mouseY = e.clientY
      }
   }

   const tapHandler = (e: MouseEvent | TouchEvent) => {
      updateMousePosition(e)
      autoAddParticle = true
   }

   const disableAutoAddParticle = () => {
      autoAddParticle = false
   }

   element.addEventListener(move, updateMousePosition, { passive: true })
   element.addEventListener(tap, tapHandler, { passive: true })
   element.addEventListener(tapEnd, disableAutoAddParticle, { passive: true })
   element.addEventListener("mouseleave", disableAutoAddParticle, {
      passive: true,
   })

   return () => {
      element.removeEventListener(move, updateMousePosition)
      element.removeEventListener(tap, tapHandler)
      element.removeEventListener(tapEnd, disableAutoAddParticle)
      element.removeEventListener("mouseleave", disableAutoAddParticle)

      const interval = setInterval(() => {
         if (animationFrame && particles.length === 0) {
            cancelAnimationFrame(animationFrame)
            clearInterval(interval)

            if (--instanceCounter === 0) container.remove()
         }
      }, 500)
   }
}

interface CoolModeProps {
   children: ReactNode
   options?: CoolParticleOptions
}

export const CoolMode: React.FC<CoolModeProps> = ({ children, options }) => {
   const ref = useRef<HTMLElement>(null)

   useEffect(() => {
      if (ref.current) return applyParticleEffect(ref.current, options)
   }, [options])

   return React.cloneElement(children as React.ReactElement, { ref })
}
